package ru.ifmo.thesis.solver.gp.dop;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import org.jgap.Chromosome;
import org.jgap.Configuration;
import org.jgap.FitnessFunction;
import org.jgap.Gene;
import org.jgap.Genotype;
import org.jgap.IChromosome;
import org.jgap.InvalidConfigurationException;
import org.jgap.Population;
import org.jgap.event.EventManager;
import org.jgap.event.IEventManager;

import ru.ifmo.thesis.domain.Job;
import ru.ifmo.thesis.domain.Shedule;
import ru.ifmo.thesis.domain.Worker;
import ru.ifmo.thesis.gp.MyConfigurationOptions;
import ru.ifmo.thesis.gp.MyFitnessFunction;
import ru.ifmo.thesis.gp.WorkPlanGene;
import ru.ifmo.thesis.gp.configurations.BaseConfiguration;
import ru.ifmo.thesis.solver.gp.CompletionRule;
import ru.ifmo.thesis.task.DOPTask;
import ru.ifmo.thesis.util.Algorithms;

public class TemplateDOPSolver {

	private static final Random random = new Random();

	private Configuration conf;
	private CompletionRule completionRule;
	private int ofPopulation;
	private MyConfigurationOptions options;

	private Genotype population;
	
	private final Class<? extends BaseConfiguration> clazz;

	public TemplateDOPSolver(CompletionRule completionRule, MyConfigurationOptions options, Class<? extends BaseConfiguration> clazz) {
		this.completionRule = completionRule;
		this.ofPopulation = options.getOfPopulation();
		this.options = options;
		this.clazz = clazz;
	}
	
	public void solve(DOPTask t) {
		try {
			List<Job> toAdd = t.getAdditionalJobs();
			List<Job> initial = t.getInitialJobs();
			
			doSetup(ofPopulation, initial, t.getWorkers()); 
			
			while (!toAdd.isEmpty()) {
				solveInternal();
				this.reset();
				initial.add(toAdd.remove(0));
				doSetup(ofPopulation, initial, t.getWorkers());
				completionRule = completionRule.newCopy();
			}
		} catch (InvalidConfigurationException e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * Simply appends specified job to first worker at last position at the job list.
	 * @param job
	 * @throws InvalidConfigurationException 
	 */
	private Genotype appendTask(Job job) throws InvalidConfigurationException {
		Population popz = new  Population(getConfiguration());
		
		for (Chromosome c : (List<Chromosome>)population.getPopulation().getChromosomes()) {
			WorkPlanGene gene = (WorkPlanGene) c.getGene(0);
			gene.appendJob(job);
			popz.addChromosome(new Chromosome(conf, c.getGenes()));
		}
		
		return new Genotype(getConfiguration(), popz);
	}

	private void solveInternal() throws InvalidConfigurationException {
		while (!completionRule.isTaskCompleted(genotypeCopy())) {
			population.evolve();
		}
	}
	
	/**
	 * Only reason for that is a side effect in GAP code.
	 * 
	 * @return
	 * @throws InvalidConfigurationException
	 */
	private Genotype genotypeCopy() throws InvalidConfigurationException {
		Population popz = new  Population(getConfiguration());
		popz.addChromosomes(population.getPopulation());
		return new Genotype(getConfiguration(), popz);
	}
	
	public Configuration getConfiguration() {
		if (conf == null) {
			setupConf();
		}
		return conf;
	}
	
	private void doSetup(int ofPopulation2, List<Job> jobs, List<Worker> workers)
			throws InvalidConfigurationException {
		
		if (getConfiguration().getSampleChromosome() == null) {
			getConfiguration().setSampleChromosome(
					createRandomChromosome(getConfiguration(), workers,
							jobs));
		}

		List<Job> tasks = jobs;
		population = createInitialPopulation(getConfiguration(), workers, tasks,
				ofPopulation2);
	}
	
	private Genotype createInitialPopulation(Configuration conf, List<Worker> workers, List<Job> tasks, int populationSize) throws InvalidConfigurationException {
		Population population = new Population(conf);
		
		for (int i = 0; i < populationSize; i++) {
			population.addChromosome(createRandomChromosome(conf, workers, tasks));
		}
		
		return new Genotype(conf, population);
	}

	private IChromosome createRandomChromosome(Configuration conf,
			List<Worker> workers, List<Job> jobs) throws InvalidConfigurationException {
		
		Gene[] myGenes = new Gene[workers.size()];

		for (int i = 0; i < workers.size(); i++) {
			myGenes[i] = new WorkPlanGene(conf, workers.get(i));
		}

		List<Job> nJobs = new ArrayList<Job>(jobs);
		
		Collections.shuffle(nJobs);
		for (Job j : nJobs) {
			int rndWorkerId = random .nextInt(myGenes.length);
			
			@SuppressWarnings("unchecked")
			ArrayList<Job> allele = (ArrayList<Job>) myGenes[rndWorkerId].getAllele();

			allele.add(j);
		}
		
		return new Chromosome(conf, myGenes);
	}

	private void setupConf() {
		setupConf(new EventManager());
	}

	private void setupConf(IEventManager eventManager) {
		try {
			Constructor<? extends BaseConfiguration> constructor = clazz.getConstructor(MyConfigurationOptions.class, IEventManager.class);
			conf = constructor.newInstance(options, eventManager);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		FitnessFunction myFunc = new MyFitnessFunction();
		try {
			conf.setFitnessFunction(myFunc);
			conf.setPopulationSize(ofPopulation);
		} catch (InvalidConfigurationException e) {
			throw new RuntimeException(e);
		}
	}

	
	public Shedule bestSolutionSoFar() {
		return Algorithms.constructShedule(population.getFittestChromosome());
	}

	public void reset() {
		IEventManager eventManager = getConfiguration().getEventManager();
		Configuration.reset();
		setupConf(eventManager);
		this.completionRule = completionRule.newCopy();
	}
}
